#pragma once

#include <d3d11.h>
#include "GraphicsManager.h"
#include <assert.h>
#include "IGrahpicsResource.h"

struct PiplineIndex;

#pragma region Class declaration
// Interface that serves as an intermediary between different ConstantBuffer<T>
class IConstantBuffer : public IGraphicsResource
{
public:
	virtual ~IConstantBuffer(void) {}

	virtual unsigned int getBufferSize(void) const = 0;
	virtual void Bind(void) = 0;
};

// Represents a Constant buffer
template<typename T>
class ConstantBuffer : public IConstantBuffer
{
protected:
	ID3D11Buffer* buffer = nullptr;

	unsigned int bufferSize;

	PiplineIndex index;

	T* bufferInfo;

public:
	ConstantBuffer(T* buffer, PiplineIndex index);
	virtual	~ConstantBuffer(void);

	/////////////////////////<Accessors>///////////////////////////////////////////////////

	virtual unsigned int getBufferSize(void) const override;

	T* getBufferInfo(void) const { return bufferInfo; }

	/////////////////////////<Public Functions>///////////////////////////////////////////////////

	virtual void Bind(void) override;


	/////////////////////<Interafce>//////////////////////////////////////////

	// copies data in video memory to CPU memory (pixel stride does nothing in this case)
	virtual void getData(void* data, unsigned int pixelStride) override;
	// copies resource data in CPU memory to video memory (pixel stride does nothing in this case)
	virtual void setData(void* data, unsigned int pixelStride) override;

	// gets the d3d11 object that represents the resource
	virtual ID3D11Resource* getResource(void) const override { return buffer; }
	// gets shader resource view for this resource
	virtual ID3D11ShaderResourceView* getSrv(void) const override { return nullptr; }

	// gets the d3d11 descriptor for this object and copies it onto 'desc'
	virtual void getDesc(void* desc) override { buffer->GetDesc((D3D11_BUFFER_DESC*)desc); }
};
#pragma endregion

#pragma region Class Definition

template<typename T>
ConstantBuffer<T>::ConstantBuffer(T* buffer, PiplineIndex index)
{
	// copy in constructor prameters
	this->bufferSize = sizeof(T);
	this->index = index;
	this->bufferInfo = buffer;

	// Create buffer
	D3D11_BUFFER_DESC buffer_desc;
	ZeroMemory(&buffer_desc, sizeof(D3D11_BUFFER_DESC));

	buffer_desc.ByteWidth = bufferSize;
	buffer_desc.Usage = D3D11_USAGE::D3D11_USAGE_DYNAMIC;
	buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

	GraphicsManager::GetInstance()->MakeBuffer(&this->buffer, &buffer_desc, buffer);
	assert(this->buffer && "Failed to create buffer!");
}

template<typename T>
/*virtual*/ unsigned int ConstantBuffer<T>::getBufferSize(void) const /*override*/ 
{
	return bufferSize; 
}

template<typename T>
/*virtual*/	ConstantBuffer<T>::~ConstantBuffer(void)
{
	if (buffer)
		buffer->Release();
}

template<typename T>
void ConstantBuffer<T>::Bind(void)
{
	// store a pointer to graphics
	GraphicsManager* pGraphics = GraphicsManager::GetInstance();

	D3D11_MAPPED_SUBRESOURCE subresource;
	ZeroMemory(&subresource, sizeof(D3D11_MAPPED_SUBRESOURCE));

	// update buffer in graphics memory
	pGraphics->getDeviceContext()->Map((ID3D11Resource*)buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &subresource);
	memcpy(subresource.pData, bufferInfo, bufferSize);
	pGraphics->getDeviceContext()->Unmap(buffer, 0);

	switch (index.shader_index)
	{
	case SHADER_INDEX_VERTEX_SHADER:
		pGraphics->getDeviceContext()->VSSetConstantBuffers(index.slot, 1, &buffer);
		break;
	case SHADER_INDEX_PIXEL_SHADER:
		pGraphics->getDeviceContext()->PSSetConstantBuffers(index.slot, 1, &buffer);
		break;
	case SHADER_INDEX_GEOMETRY_SHADER:
		pGraphics->getDeviceContext()->GSSetConstantBuffers(index.slot, 1, &buffer);
		break;
	}
}

template<typename T>
/*virtual*/ void ConstantBuffer<T>::getData(void* data, unsigned int pixelStride) /*override*/
{
	memcpy(data, bufferInfo, sizeof(T));
}

template<typename T>
/*virtual*/ void ConstantBuffer<T>::setData(void* data, unsigned int pixelStride) /*override*/
{
	memcpy(bufferInfo, data, sizeof(T));
}
#pragma endregion